package whf.framework.xml;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.io.StringWriter;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.beanutils.MethodUtils;
import org.apache.commons.beanutils.PropertyUtils;
import org.dom4j.Document;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.XMLWriter;
import org.jdom.CDATA;
import org.jdom.Element;
import org.jdom.output.XMLOutputter;

import whf.framework.entity.Entity;
import whf.framework.exception.AppException;
import whf.framework.i18n.ApplicationResource;
import whf.framework.log.Log;
import whf.framework.log.LogFactory;
import whf.framework.meta.MetaFactory;
import whf.framework.service.Service;
import whf.framework.util.BeanFactory;
import whf.framework.util.BeanUtils;
import whf.framework.util.StringUtils;
import whf.framework.util.TypeUtils;

/**
 * @author wanghaifeng
 * @create Mar 27, 2007 9:03:11 PM
 * 
 */
public class XmlHttpServlet extends HttpServlet {
	private static Log log = LogFactory.getLog(XmlHttpServlet.class);
	
	private String encode = "GBK";
	
	public void init(ServletConfig config) throws ServletException {
		super.init(config);
		if(!StringUtils.isEmpty(config.getInitParameter("encode")))
			this.encode = config.getInitParameter("encode");
	}

	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		String[] returnArr = null;
		Object returnObj = null;
		try {
			String serviceName = request.getParameter("serviceName");
			String methodName = request.getParameter("methodName");
			
			String propertyString = request.getParameter("properties");
			String[] properties = StringUtils.split(propertyString, ",");
			
			String[] params = request.getParameterValues("param");
			
			int paramsCount = params == null?0: params.length;
			if(params != null && params.length == 1 && "void".equalsIgnoreCase(params[0])) paramsCount = 0;
			
			if(serviceName.equalsIgnoreCase("sys")){
				if(methodName.equalsIgnoreCase("i18n")){
					if(paramsCount == 0){
						returnObj = ApplicationResource.get(propertyString);
					} else {
						returnObj = ApplicationResource.get(propertyString, params);
					}
				}
			} else if (serviceName.equalsIgnoreCase("request")
					|| serviceName.equalsIgnoreCase("session")) {		//从session中或者request中获取值
				returnObj = this.getRequestValue(serviceName, methodName, request);
			} else if (methodName.equals("setSession")) {				//调用session中的setAttribute方法设置属性
				returnObj = this.setSession(request, params);
			} else {													//执行Spring中的一个特定的service的方法
				Service service = BeanFactory.getService(serviceName);
				Class serviceClass = service.getClass();
				Method[] methods = serviceClass.getMethods();
				Class[] methodParamTypes = null;
				boolean found = false;
				for (int i = 0; i < methods.length; i++) {
					if (methods[i].getName().equals(methodName)) {
						if(methods[i].getParameterTypes().length == paramsCount){
							methodParamTypes = methods[i].getParameterTypes();
							found = true;
							break;
						}
					}
				}
				if (!found) {
					throw new XmlHttpException("remote function：" + methodName + " not found！");
				}
				Object[] methodParameters = convertStringParams2MethodParams(params, methodParamTypes);
				// 解析返回值
				try{
					if(paramsCount == 0){
						returnObj = MethodUtils.invokeMethod(service, methodName, null);
					} else {
						returnObj = MethodUtils.invokeMethod(service, methodName, methodParameters);
						//service.invokeMethod(methodName, methodParamTypes, methodParameters);
					}
				}catch(Exception e){
					if(e instanceof AppException)
						returnObj = new String[]{e.toString()};
					else
						throw e;
				}
			}
			returnObj = parseReturn(returnObj, properties);
		} catch (XmlHttpException e) {
			returnArr = new String[2];
			returnArr[0] = "0";
			returnArr[1] = e.getMessage();
		} catch (InvocationTargetException e) {
			returnArr = new String[2];
			returnArr[0] = "0";
			returnArr[1] = e.getCause().getMessage();
		} catch (Exception e) {
			returnArr = new String[2];
			returnArr[0] = "0";
			returnArr[1] = e.getMessage();
		}
		// 使用xml包装返回值
		if (returnObj == null) {
			processResponse(response, returnArr);
		} else {
			processResponse(response, returnObj);
		}

	}

	private void upload(HttpServletRequest request, HttpServletResponse response)
			throws IOException {
		String fileName = request.getParameter("fileName");

		String realPath = this.getServletContext().getRealPath("/");
		String realFile = realPath + "wf" + File.separator + "WebFlow"
				+ File.separator + "flows" + File.separator + fileName;
		InputStream is = request.getInputStream();
		OutputStream os = new FileOutputStream(new File(realFile));

		byte[] b = new byte[2048];
		int len;
		while ((len = is.read(b)) > 0) {
			os.write(b, 0, len);
		}
		is.close();

		os.flush();
		os.close();
	}

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		if ("upload".equals(request.getParameter("mode"))) {
			this.upload(request, response);
		}
	}

	private String setSession(HttpServletRequest request, String[] params)
			throws Exception {
		if (params == null || params.length != 2) {
			throw new Exception("使用错误，请指定存入SESSION的属性名和属性值！");
		}
		request.getSession().setAttribute(params[0], params[1]);
		return params[1];
	}

	/**
	 * 从session或request中获取信息
	 * 
	 * @param serviceName
	 * @param methodName
	 * @param request
	 * @return
	 */
	private Object getRequestValue(String serviceName, String methodName,
			HttpServletRequest request) {
		// Attribute Name
		String attrName = methodName;
		String propName = "";
		if (methodName.indexOf(".") != -1) {
			attrName = methodName.substring(0, attrName.indexOf("."));
			propName = methodName.substring(methodName.indexOf(".") + 1);
		}
		Object obj = null;
		if (serviceName.equalsIgnoreCase("request")) {
			obj = request.getAttribute(attrName);
		}
		if (serviceName.equalsIgnoreCase("session")) {
			obj = request.getSession().getAttribute(attrName);
		}
		if (propName.length() == 0) {
			return "" + obj;
		} else if (obj == null) {
			return "" + null;
		} else {
			try {
				return "" + PropertyUtils.getProperty(obj, propName);
			} catch (Exception e) {
				log.warn(e);
			}
		}
		return "" + null;
	}

	private void processResponse(HttpServletResponse response, Object returnObj) {
		try {
			if (returnObj instanceof Document){
				String returnXmlStr = dom2String((Document) returnObj);
				log.debug(returnXmlStr);
				response.setContentType("text/xml");
				OutputStream out = response.getOutputStream();
				out.write(returnXmlStr.getBytes(this.encode));
				out.close();
			} else {
				String[] infor = (String[])returnObj;
				Element root = new Element("response");
				Element sizeEle = new Element("size");
				sizeEle.setText(String.valueOf(infor == null?0: infor.length));
				root.addContent(sizeEle);
				
				for(int i=0; infor != null && i< infor.length; i++){
					Element ele = new Element("infor");
					ele.setAttribute("id", String.valueOf(i));
					CDATA cdata = new CDATA(infor[i]);
					ele.addContent(cdata);
					root.addContent(ele);
				}
				
				XMLOutputter outputter = new XMLOutputter();
				OutputStream out = response.getOutputStream();
				org.jdom.Document doc = new org.jdom.Document(root); 
				outputter.output(doc, out);
				out.close();
				outputter.output(doc, System.out);
			}
		} catch (Exception e) {
			log.error("xml http 回传数据失败", e);
		}
	}

	private String dom2String(Document doc) {
		StringWriter sw = new StringWriter();
		OutputFormat format = new OutputFormat();
		format.setEncoding(this.encode);
		XMLWriter writer = new XMLWriter(sw, format);
		try {
			writer.write(doc);
			writer.flush();
		} catch (Exception e) {
			throw new RuntimeException("Document 转化为 String 失败", e);
		}
		return sw.toString();
	}
	
	private String describe(Object obj, String[] properties){
		if(obj == null) return "";
		if(properties == null && obj instanceof Entity) properties = new String[]{"id","name"};
		if(properties == null) return obj.toString();
		StringBuilder sb = new StringBuilder();
		for(String prop: properties){
			try{
				sb.append(BeanUtils.getProperty(obj, prop)).append(",");
			}catch(Exception e){
				sb.append(obj).append(",");
			}
		}
		String res = sb.toString();
		if(res.endsWith(",")) res = res.substring(0, res.length() - 1);
		if(res.startsWith(",")) res = res.substring(1);
		return res;
	}

	/**
	 * @param returnObj
	 * @param properties
	 * @return 如果多对象，每个对象置入一个数组中，如果是单对象属性间使用；号隔开
	 */
	private Object parseReturn(Object returnObj, String[] properties) {
		String[] buff = null;
		if(properties == null) properties = new String[]{"id","name"};
		if (returnObj != null) {
			if (returnObj instanceof Document) {
				return returnObj;
			} else if (returnObj.getClass().isArray()) {
				int len = Array.getLength(returnObj);
				buff = new String[len];
				for (int i = 0; i < len; i++) {
					buff[i] = Array.get(returnObj, i).toString();
				}
			} else if (returnObj instanceof Collection) {	// 对象为集合
				Collection returns = (Collection) returnObj;
				Object[] temp = returns.toArray();
				buff = new String[temp.length];
				for (int i = 0; i < temp.length; i++) {
					buff[i] = this.describe(temp[i], properties);
				}
			} else if (returnObj instanceof Map) {
				// 对象为Map
				Map map = (Map) returnObj;
				buff = new String[map.size()];
				Iterator it = map.keySet().iterator();
				int i = 0;
				Object key;
				while (it.hasNext()) {
					key = it.next();
					buff[i] = key.toString() + ":" + this.describe(map.get(key), properties);
					i++;
				}
			} else {
				buff = new String[1];
				buff[0] = this.describe(returnObj, properties);
			}
		}
		String[] result = new String[1 + (buff == null ? 0 : buff.length)];
		result[0] = String.valueOf(buff.length);
		if (buff != null) {
			System.arraycopy(buff, 0, result, 1, buff.length);
		}
		return result;
	}

	/**
	 * 将字符串类型的参数转换为方法对应的类型
	 * @modify wanghaifeng Mar 28, 2007 10:41:48 PM
	 * @param params
	 * @param paramTypeList
	 * @return
	 * @throws XmlHttpException
	 */
	private Object[] convertStringParams2MethodParams(String[] params, Class[] paramTypes)
			throws XmlHttpException {
		if(params == null || params.length == 0) return null;
		Object[] result = new Object[params.length];
		for(int i=0; i< paramTypes.length; i++){
			Class paramType = paramTypes[i];
			String param = params[i];
			try{
				if (Integer.TYPE == paramType) {
					result[i] = new Integer(param);
				} else if (Double.TYPE == paramType) {
					result[i] = new Double(param);
				} else if (Boolean.TYPE == paramType) {
					result[i] = new Boolean(param);
				} else if (Long.TYPE == paramType) {
					result[i] = new Long(param);
				} else if (Float.TYPE == paramType) {
					result[i] = new Float(param);
				} else if (Character.TYPE == paramType) {
					throw new XmlHttpException("暂不支持参数类型为char的处理！");
				} else if (Byte.TYPE == paramType) {
					result[i] = new Byte(param);
				} else if (Short.TYPE == paramType) {
					result[i] = new Short(param);
				} else if(Serializable.class == paramType) {
					result[i] = new Long(param);
				} else if(String.class == paramType) {
					result[i] = param;
				} else if(TypeUtils.isImplementsFrom(paramType, Service.class)){
					long id = Long.parseLong(param);
					Service service = BeanFactory.getService(paramType);
					result[i] = service.findByPrimaryKey(id);
				} else if(TypeUtils.isImplementsFrom(paramType, Entity.class)){
					long id = Long.parseLong(param);
					Service service = BeanFactory.getService(MetaFactory.findByModelClass(paramType));
					result[i] = service.findByPrimaryKey(id);
				} else {
					result[i] = null;
					log.warn("Not support param type!");
				}
			}catch(Exception e){
				throw new XmlHttpException(e);
			}
		}
		return result;
	}

	// Clean up resources
	public void destroy() {
	}
}